use std::collections::HashSet;

use ndarray::{array, Array2};

pub(crate) use crate::tests::fixtures::{
    rand_bool,
    rand_usize,
    random_connectors,
    random_missing_tile_policy,
    random_predecessor,
    random_tile_selection_policy,
    random_tile_text,
    random_tile_type,
    random_vacancy_policy,
    RANDOM_EXCLUDED_CONNECTORS,
    RANDOM_TILES,
    RANDOM_USIZE,
};
use crate::tests::fixtures::{RANDOM_DIRECTION, RANDOM_EXPOSED_EDGES, RANDOM_PREDECESSOR, RANDOM_USIZES, RANDOM_USIZES_SMALL};
use crate::tiling::tiles::tile_list::TileList;
use crate::tiling::tiles::{ArrayInitializable, Connectors, TileConnectionArray};
use crate::tiling::{Direction, GridSlot, SlotGrid};

mod direction_counterpart_offset_mapping;
mod seeking_directions;
mod slot_grid;

pub fn create_grid_slot_with_predecessor(predecessor: Option<(usize, usize)>) -> Option<GridSlot> {
    Some(GridSlot {
        tile_id: Some(rand_usize(1, 20)),
        direction: Some(Direction::East),
        predecessor,
        exposed_edges: vec![],
        excluded_connectors: HashSet::new(),
    })
}

pub(crate) fn create_grid_slot_with_tile_id(tile_id: Option<usize>) -> Option<GridSlot> {
    Some(GridSlot {
        tile_id,
        direction: Some(Direction::East),
        predecessor: Some((rand_usize(1, 17), rand_usize(1, 7))),
        exposed_edges: vec![Direction::West, Direction::South],
        excluded_connectors: HashSet::from([random_connectors(), random_connectors()]),
    })
}

pub(crate) fn create_grid_slot_with_some_tile_id(tile_id: usize) -> Option<GridSlot> {
    create_grid_slot_with_tile_id(Some(tile_id))
}

fn create_grid_slot() -> Option<GridSlot> {
    match rand_bool() {
        true => create_grid_slot_with_some_tile_id(rand_usize(0, 12)),
        false => create_grid_slot_with_tile_id(None),
    }
}

pub(crate) fn create_grid_slot_or_none() -> Option<GridSlot> {
    match rand_bool() {
        true => None,
        false => create_grid_slot(),
    }
}

pub(crate) fn create_reproducible_grid_slot_with_tile_id(tile_id: Option<usize>) -> Option<GridSlot> {
    Some(GridSlot {
        tile_id,
        direction: *RANDOM_DIRECTION,
        predecessor: *RANDOM_PREDECESSOR,
        exposed_edges: RANDOM_EXPOSED_EDGES[*RANDOM_USIZE].clone(),
        excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[RANDOM_USIZES_SMALL[4]].clone(),
    })
}

pub(crate) fn create_reproducible_grid_slot_with_tile_id_no_excl_connectors(tile_id: Option<usize>) -> Option<GridSlot> {
    Some(GridSlot {
        tile_id,
        direction: *RANDOM_DIRECTION,
        predecessor: *RANDOM_PREDECESSOR,
        exposed_edges: RANDOM_EXPOSED_EDGES[*RANDOM_USIZE].clone(),
        excluded_connectors: HashSet::new(),
    })
}

pub(crate) fn create_reproducible_slot_with_fields(
    direction: Option<Direction>,
    predecessor: Option<(usize, usize)>,
    exposed_edges: Vec<Direction>,
) -> Option<GridSlot> {
    Some(GridSlot {
        tile_id: Some(RANDOM_USIZES[7]),
        direction,
        predecessor,
        exposed_edges,
        excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[0].clone(),
    })
}

pub(crate) fn pad_grid_randomly(grid: Array2<Option<GridSlot>>) -> Array2<Option<GridSlot>> {
    let padding = *RANDOM_USIZE;
    let (old_row_count, old_col_count) = grid.dim();
    let new_row_count = old_row_count + padding * 3;
    let new_col_count = old_col_count + padding * 4;

    let mut new_grid = Array2::from_elem((new_row_count, new_col_count), None);

    for row in 0..old_row_count {
        for col in 0..old_col_count {
            new_grid[[row + padding, col + padding * 2]] = grid[[row, col]].clone();
        }
    }

    new_grid
}

pub(crate) fn pad_coordinates_randomly(coordinates: (usize, usize)) -> (usize, usize) {
    let padding = *RANDOM_USIZE;
    (coordinates.0 + padding, coordinates.1 + padding * 2)
}

fn create_test_grid(use_random_tile_ids: bool) -> Array2<Option<GridSlot>> {
    array![
        [None, None, None],
        [
            None,
            Some(GridSlot {
                tile_id: Some(
                    if use_random_tile_ids {
                        *RANDOM_USIZE
                    } else {
                        4
                    }
                ),
                direction: Some(Direction::North),
                predecessor: Some((2, 1)),
                exposed_edges: vec![],
                excluded_connectors: HashSet::new(),
            }),
            None,
        ],
        [
            None,
            Some(GridSlot {
                tile_id: Some(
                    if use_random_tile_ids {
                        *RANDOM_USIZE + 3
                    } else {
                        0
                    }
                ),
                direction: Some(Direction::East),
                predecessor: Some((2, 2)),
                exposed_edges: vec![],
                excluded_connectors: HashSet::new(),
            }),
            Some(GridSlot {
                tile_id: None,
                direction: None,
                predecessor: Some((3, 2)),
                exposed_edges: vec![Direction::North],
                excluded_connectors: HashSet::new(),
            }),
        ],
        [
            None,
            None,
            Some(GridSlot {
                tile_id: Some(
                    if use_random_tile_ids {
                        *RANDOM_USIZE + 7
                    } else {
                        1
                    }
                ),
                direction: Some(Direction::South),
                predecessor: Some((1, 1)),
                exposed_edges: vec![],
                excluded_connectors: HashSet::new(),
            }),
        ],
    ]
}

pub(crate) fn create_test_slot_grid(use_random_tiles: bool, pad_grid: bool) -> SlotGrid {
    let tiles = if use_random_tiles {
        RANDOM_TILES.iter().cloned().collect()
    } else {
        vec![
            random_tile_text(Connectors {
                north: rand_usize(0, 10),
                east: *RANDOM_USIZE + 1,
                south: rand_usize(0, 10),
                west: rand_usize(0, 10),
            }),
            random_tile_text(Connectors {
                north: *RANDOM_USIZE + 5,
                east: rand_usize(0, 10),
                south: rand_usize(0, 10),
                west: *RANDOM_USIZE + 4,
            }),
            random_tile_text(random_connectors()),
            random_tile_text(Connectors {
                north: *RANDOM_USIZE + 6,
                east: *RANDOM_USIZE + 2,
                south: *RANDOM_USIZE + 9,
                west: *RANDOM_USIZE + 10,
            }),
            random_tile_text(Connectors {
                north: rand_usize(0, 10),
                east: rand_usize(0, 10),
                south: *RANDOM_USIZE + 11,
                west: rand_usize(0, 10),
            }),
        ]
    };
    let tile_list = TileList {
        tiles,
        horizontal_connector_names: vec![],
        vertical_connector_names: vec![],
        tile_type: random_tile_type(),
    };
    let mut grid = create_test_grid(use_random_tiles);
    if pad_grid {
        grid = pad_grid_randomly(grid)
    }
    SlotGrid {
        grid,
        num_rows: 3,
        num_cols: 2,
        border_tile_id: Some(
            if use_random_tiles {
                *RANDOM_USIZE + 5
            } else {
                3
            },
        ),
        tile_connections: TileConnectionArray::new(&tile_list.tiles),
        tile_list,
        missing_tile_policy: random_missing_tile_policy(),
        tile_selection_policy: random_tile_selection_policy(),
        vacancy_policy: random_vacancy_policy(),
        current_slot: (rand_usize(0, 11), rand_usize(0, 6)),
        current_predecessor: random_predecessor(),
        last_leaves: vec![
            (rand_usize(0, 10), rand_usize(0, 9)),
            (rand_usize(0, 10), rand_usize(0, 9)),
        ],
        exposed_edge_count: rand_usize(0, 3),
    }
}
